jpackage('twc.appointments', function(){
    ////
    // models
    var Appointment = Backbone.Model.extend({
        idAttribute: 'billingOrderNumber',
        url: function(){
            return twc.routes.get('appointments/cancel', { orderNumber: this.id });
        },

        isToday:function(){
            var date = this.parseDate('arrivalTime');
            var today = new Date();

            // TODO UTC ?? what tzone is date data...
            return _.all(['getDate', 'getMonth', 'getFullYear'], function(method){
                return date[method]() == today[method]();
            });
        },

        isCancellable:function(){
            // TODO integrate with global apptsDivisionCancelStatus
            var cancellable = this.get('appointmentRules').isCancellable;
            var bsys = this.get('billingSystem') || {};

            if (bsys.billingSystemTypeCode == "CSG" && this.get('jobClass') == "D")
                cancellable = false;
            else if (bsys.billingSystemTypeCode == "ICOMS" && (this.get('billingOrderType') == "DW" || this.get('billingOrderType') == "DI"))
                cancellable = false;

            return cancellable;
        },

        isRescheduleable:function(){
            // TODO integrate with global apptsDivisionRescheduleStatus
            return this.get('appointmentRules').isRescheduleable;
        },

        viewAttributes:function(){
            var aTime = this.get('arrivalTime');

            return {
                type: this.get('appointmentType'),
                orderNumber: this.get('billingOrderNumber'),
                date: twc.utils.formatDate(aTime.date),
                time: twc.utils.formatTime(aTime.time)
            };
        },

        getDateTime:function(){
            return twc.utils.parseDate(this.get('arrivalTime').date);
        },

        collectReminders:function(types){
            types = types || ['DayBefore'];

            return _.filter(this.get('reminders'), function(r){
                return _.include(types, r.reminderType);
            });
        }
    });

    var Appointments = Backbone.Collection.extend({
        model: Appointment,
        url: twc.routes.get('appointments/orders'),

        today:function(){
            return this.find(function(appt){
                return appt.isToday();
            });
        },

        upcoming:function(){
            return this.select(function(appt){
                return !appt.isToday();
            });
        },

        fetch:function(){
            return Backbone.Collection.prototype.fetch.call(this, { data: this.request ? this.request.attributes : null, cache: false, reset: true });
        }
    });


    ////
    // views
    var BaseView = Backbone.View.extend({
        initialize:function(opts){
            this.isAccount = !!opts.account;
            this.appointments = opts.appointments;

            this.appointments.on('reset', this.render, this);
            this.appointments.on('fetch', this.onFetch, this);

            if (this.renderError){
                this.appointments.on('error', this.renderError, this);
            }
        }
    });

    var ResultNodeView = Backbone.View.extend({
        tagName: 'li',

        initialize:function(opts){
            this.parent = opts.parent; // parent ResultView instance

            this.model.on('change', this.render, this);
        },

        render:function(index){
            if (index !== undefined){
                this.index = index;
                this.$el.addClass('upcoming');
            }

            this.$el.html(twc.utils.template('appointment-results-node')({
                index: index,
                reminders: this.model.collectReminders(),
                appointment: this.model.viewAttributes(),
                reschedulable: this.model.isRescheduleable(),
                cancelable: this.model.isCancellable(),
                cancelState: this.model.isCancellable() ? this._cancelState : undefined
            }));

            return this;
        },

        remove:function(){
            return Backbone.View.prototype.remove.apply(this, arguments);
        },

        setCancelState:function(state){
            this._cancelState = state;
            this.render(this.index);
        },

        renderReschedule:function(ev){
            var parent = this.parent;
            var opts = {
                parent: this.parent,
                dates: new twc.appointments.RescheduleDates({
                    original: this.model
                })
            };

            setTimeout(function(){
                twc.utils.setLoader(ev.target);
                parent.disableButtons();
            }, 0);

            if (this.parent.requestError){
                this.parent.requestError.remove();
                delete this.parent.requestError;
            }

            this.rescheduler = new twc.appointments.RescheduleView(opts);
            this.rescheduler.on('remove', function(){
                var schedulerEl = this.rescheduler.$el;

                this.parent.appointments.fetch({ reset: true }).always(function(){
                    schedulerEl.remove();
                });
            }, this);

            // bind dates error to parent
            opts.dates.on('error', this.parent.renderError, this.parent);
            opts.dates.on('error', function(){
                twc.utils.clearLoader(ev.target);
            });

            opts.dates.fetch({ reset: true });
        },

        renderConfirmCancel:function(){
            this.setCancelState('confirming');
            this.parent.hideAll(this.el);
        },

        events: {
            // after cancel is complete -- rerender ResultView
            'click button[data-action="view-all"]':function(){
                this.parent.render();
            },

            // reschedule button
            'click button[data-action="reschedule"]':'renderReschedule',

            // cancel link -- starts cancel process
            'click button[data-action="cancel"]':'renderConfirmCancel',

            // cancel button -- confirms cancel
            'click button[data-action="cancel-confirm"]':function(){
                this.setCancelState('confirmed');
                this.model.destroy().fail(function(){
                    twc.omniture.track('error', 'Appoinment Manager: Cancellation error');
                });
            },

            // keep appointment
            'click button[data-action="cancel-keep"]':function(){
                this.setCancelState();
                this.parent.showAll();
            }
        }
    });

    var ResultView = BaseView.extend({
        addNode:function(appt, index){
            var node = new ResultNodeView({ model: appt, parent: this }).render(index);

            this.$el.list.append(node.el);
            this.nodes.push(node);
        },

        render:function(){
            twc.utils.clearLoader(this.$el);

            if (!this.appointments.length){
                if (!this.isAccount) return;

                if (this.$el.list){
                    this.$el.list.remove();
                    delete this.$el.list;
                }

                var tmpl = twc.utils.template('appointment-empty')();

                this.$el.find('img').remove();
                this.$el.find('h1').addClass('pull-left');
                this.$el.removeClass('appointments-results').addClass('empty').append(tmpl);

                return this;
            }

            var nodes = this.nodes || (this.nodes = []);
            var $el = this.$el;

            $el.addClass('appointments-results');

            var today = this.appointments.today();
            var upcoming = this.appointments.upcoming();
            var extra = $.trim(twc.utils.template('appointment-extra')());

            if ($el.list){
                $el.list.show();

                // remove any existing nodes
                for (var i = 0, node; node = nodes[i]; i++)
                    node.remove();

            } else {
                // remove all children except the header
                var children = $el.children();
                var header = children.filter('h1, h2').removeClass('pull-left');

                children.not(header).remove();

                // create list el
                $el.list = $('<ol class="appointment-results">')
                //$el.list.hide().appendTo(this.el);
                $el.list.appendTo(this.el);
            }

            // add today's appointment, if any
            if (today)
                this.addNode(today);

            // add all upcoming appointments (if any)
            if (upcoming.length){
                for (var i = 0, appt; appt = upcoming[i]; i++){
                    this.addNode(appt, i);
                }
            }

            // extra must always be jQuery object
            //$el.extra = $el.extra || (extra ? $(extra).hide().appendTo(this.el) : $());
            //$el.list.slideDown(350, function(){
                //$el.extra.slideDown(350);
            //});


            $el.extra = $el.extra || (extra ? $(extra).appendTo(this.el) : $());

            return this;
        },

        onFetch:function(){
            if (this.appointments instanceof Appointments)
                twc.utils.setLoader(this.$el);
        },

        renderError:function(collection, response){
            // this may be bubbled up from Reschedule validation failure...
            if (!response || !response.responseText) return;
            if (response.status == 500 && this.isAccount){
                var opts = { message: twc.copy.get('appointments/error') };
                var tmpl = twc.utils.template('data-error');

                twc.utils.clearLoader(this.$el);

                this.$el.append(tmpl(opts));
                this.$el.find('img').remove();

            }
        },

        hideAll:function(except){
            var nodes = this.$el.list.children();

            for (var i = 0, li; li = nodes[i]; i++)
                if (except != li) $(li).hide();

            this.$el.find('.extra').hide();
        },

        showAll:function(){
            this.$el.find('button').prop('disabled', false);
            this.$el.find('.extra').show();
            this.$el.list.children().show();
        }
    });

    // explicit exports
    this.Model = Appointment;
    this.Collection = Appointments;
    this.BaseView = BaseView;
    this.ResultView = ResultView;
});

jpackage('twc.appointments', function(){
    var attributeNames = { email: 'email address', sms: 'phone number', ivr: 'phone number' };
    var reminderTypes  = { text: 'SMS', phone: 'IVR', email: 'Email' };
    var reminderMethods = _.invert(reminderTypes);

    var omniName = 'services : my services : appointment manager : reschedule submitted';
    var omniProps = {
        p54: omniName, p63: omniName,
        p64: omniName, e63: omniName,
        events: 'event91, event92'
    };

    function createReminder(method, value){
        var type = 'DayBefore';

        if (method === false){
            value = '';
            type = 'OptOut';
        } else {
            method = reminderTypes[method];
        }

        return { contactMethod: method || '', contactValue: value, reminderType: type };
    }

    var RescheduleDate = Backbone.Model.extend({
        url: twc.routes.get('appointments/reschedule'),
        email: /.+\@.+\..+/, sms: /[0-9]{10}/, ivr: /[0-9]{10}/,

        validate:function(attrs){
            var errs = _.chain(attrs.reminders).map(function(reminder){
                if (reminder.reminderType == 'OptOut') return;

                var method = reminder.contactMethod;
                var attr = method.toLowerCase();
                var val  = reminder.contactValue;

                //if (!val) return (method +' is required.');
                if (!this[attr].test(val)) return 'Please enter a valid '+ attributeNames[attr];

            }, this).compact().value();

            if (errs.length)
                return errs[0];
        },

        // Used only by the Confirmation view
        viewAttributes:function(){
            return {
                date: twc.utils.formatDate(this.get('scheduleDate')),
                arrival: twc.utils.formatTime(this.get('scheduleTimePretty')),
                code: this.get('billingOrderNumber'),
                reminders: this.getMergedReminders()
            }
        },

        // Used by viewAttributes method
        getMergedReminders:function(){
            var reminders = this.get('reminders');
            var originals = this.collection.original.collectReminders();
            var results = {};

            if (originals.length < 1)
                return null;

            _.each(originals, function(original){
                var reminder = _.find(reminders, function(r){
                    return r.contactMethod == original.contactMethod
                }) || original;

                results[reminder.contactMethod.toLowerCase()] = reminder.contactValue;
            });

            return results;
        }
    });

    this.RescheduleDates = Backbone.Collection.extend({
        model: RescheduleDate,

        initialize:function(options){
            this.original = options.original;
        },

        url: function(){
            return twc.routes.get('appointments/schedule', { orderNumber: this.original.id, ts: +new Date });
        },

        compareDate:function(date){
            var time = date.getTime();

            return function(model){
                var date = model.parseDate('scheduleDate');
                return date && +date == time;
            };
        },

        selectByDate:function(d){
            return this.select(this.compareDate(d));
        },

        findByDate:function(d){
            return this.find(this.compareDate(d));
        },

        includesMonth:function(m){
            return !!this.find(function(model){
                var date = model.parseDate('scheduleDate');
                return date && date.getMonth() == m;
            });
        }
    });

    this.RescheduleView = Backbone.View.extend({
        className: 'appointment-reschedule',

        initialize:function(opts){
            this.parent = opts.parent;
            this.dates = opts.dates;

            this.dates.on('fetch', this.onFetch, this);
            this.dates.on('reset', this.render, this);
            this.dates.on('error', this.renderError, this);
            this.dates.on('sync', this.renderConfirm, this);

            this.originalTime = twc.utils.formatTime(this.dates.original.get('arrivalTime').time);
        },

        initDatepicker:function(){
            var date = this.dates.original.get('arrivalTime').date;
            var opts = { 'date-format': 'yyyy-mm-dd', date: date };

            var elem = this.$el.find('.date-picker').data(opts).datepicker();

            this.datepicker = elem.data('datepicker');
            this.timePicker = this.$el.find('.time-picker');

            // move picker element to the date-picker container and show it
            this.datepicker.picker.addClass('datepicker-static').prependTo(elem);

            // show picker
            this.datepicker.show();

            // unbind datepicker events so it doesnt hide. ever.
            $(document).off('mousedown.datepicker');
            elem.off('click.datepicker');

            // setup date picker event handling
            this.datepicker.element.on('render', _.bind(function(){
                var date = this.datepicker.date;
                var viewDate = this.datepicker.viewDate;

                this.times = null;
                this.renderDates();

                if (date.getMonth() == viewDate.getMonth())
                    this.times = this.dates.selectByDate(date);

                this.renderTimes();
            }, this)).trigger('render');
        },

        render:function(){
            var original  = this.dates.original;
            var reminders = original.collectReminders();

            var keys   = _.map(reminders, function(r){ return r.contactMethod.toLowerCase() });
            var values = _.map(reminders, function(r){ return r.contactValue });

            // hide the result list and extra text
            this.parent.$el.list.hide();
            this.parent.$el.extra.hide();

            this.$el.html(twc.utils.template('appointment-reschedule')({
                appointment: original.viewAttributes(),

                reminders: keys.length ? _.object(keys, keys) : null,
                reminderValues: _.object(keys, values)
            }));

            this.$el.append(twc.utils.template('appointment-extra')());
            this.$el.appendTo(this.parent.el);

            this.initDatepicker();

            this.submit = this.$el.find('button[data-action="submit"]');

            return this;
        },

        renderDates:function(){
            var dates = this.dates;
            var picker = this.datepicker.picker;
            var viewDate = this.datepicker.viewDate;

            _.each({ prev: -1, next: 1 }, function(inc, type){
                var date = new Date(viewDate);
                var elem = picker.find('.' + type);

                date.setDate(1);
                date.setMonth(date.getMonth() + inc);

                if (dates.includesMonth(date.getMonth())){
                    elem.removeClass('disabled').children().show();
                } else {
                    elem.addClass('disabled').children().hide();
                }
            });

            picker.find('.switch').attr('class', null);
            picker.find('table td.day').each(function(index,el){
                var elem = $(el);
                var date = elem.data('date');

                if (!dates.findByDate(date) || elem.is('.new, .old'))
                    elem.addClass('disabled').removeClass('active');
            });
        },

        renderTimes:function(){
            var view = this;
            var timepicker = view.timePicker.empty();

            if (!this.times || !this.times.length){
                timepicker.css({ border: 0 });
                return;
            }

            var origDate = view.dates.original.getDateTime();
            var origTime = view.originalTime;

            var isOriginalDate = (origDate && origDate.getTime() == view.datepicker.date.getTime());

            timepicker.attr('style', null);

            _.each(this.times, function(model){
                var time = twc.utils.formatTime(model.get('scheduleTimePretty'));

                var label = $('<label class="radio"></label>');
                var input = $('<input>', {
                    type: 'radio', name: 'time', checked: (isOriginalDate && time == origTime)
                }).on('change', function(){ view.selected = model; });

                label.append('<span>'+ time +'</span>');

                input.prependTo(label);
                label.appendTo(timepicker);
            });
        },

        renderError:function(model, err){
            twc.omniture.track('error', 'Appointment Manager: Rescheduling error');

            if (err && err.responseText){
                this.remove();

            } else {
                var tmpl = twc.utils.template('appointment-error')({ error: err });

                twc.utils.clearLoader(this.$el.find('button'));

                this.timePicker.addClass('error');
                this.$el.find('.form-horizontal').before(tmpl);
            }
        },

        renderConfirm:function(model){
            if (model === this.dates)
                return;

            this.$el.html(twc.utils.template('appointment-reschedule-confirmed')({
                original: this.dates.original.viewAttributes(),
                rescheduled: this.selected.viewAttributes()
            }));
        },

        clearError:function(){
            this.$el.find('.alert').remove();
            this.timePicker.removeClass('error');
        },

        submit:function(ev){
            this.clearError();

            if (this.selected){
                var originals = this.dates.original.collectReminders();
                var attrs = this.formAttributes(originals);
                var props = _.extend({
                    e2: _.map(['text', 'email', 'phone'], function(t){
                        return _.any(originals, function(r){
                            return r.contactMethod.toLowerCase() == t;
                        }) ? t : '';
                    }).join(':')
                }, omniProps);

                twc.omniture.track('event', props);
                twc.utils.setLoader(this.submit);

                this.disableButtons();
                this.selected.save(attrs);

            } else {
                var err = 'Please select a new date and time for your appointment';
                var tmpl = twc.utils.template('appointment-error')({ error: err });

                this.$el.find('.reschedule-date-time').after(tmpl);
                this.timePicker.addClass('error');
            }

        },

        remove:function(ev){
            if (ev) twc.utils.setLoader(ev.target);

            this.trigger('remove');
            return false;
        },

        formAttributes:function(originals){
            var reminders = [];

            _.each(['text', 'phone', 'email'], function(field){
                var selector = 'input[name^="'+ field +'"]';

                if (this.$el.find(selector).length){
                    var value = twc.utils.collectValues(this.$el, selector);
                    var original = _.find(originals, function(r){
                        return r.contactMethod == reminderTypes[field];
                    });

                    if (original && original.contactValue != value){
                        reminders.push(createReminder(field, value));
                    }
                }
            }, this);

            return { reminders: reminders };
        },

        events:{
            // submit reschedule request
            'click button[data-action="submit"]': 'submit',
            'keypress input': function(ev){
                if (ev.which == 13)
                    this.submit();
            },

            // cancel changes link
            'click button[data-action="cancel-reschedule"]': function(ev){
                twc.omniture.track('event', 'p19', {
                    p19: 'Appointment Manager: Reschedule: Cancel Changes'
                });

                this.disableButtons();
                this.remove(ev);
            },

            // confirmation view all
            'click button[data-action="view-all"]': 'remove'
        }
    });
});


